#include <wx/wxprec.h>
#include <wx/wx.h>
#include <wx/splitter.h>
#include <wx/notebook.h>
#include <wx/listctrl.h>
#include <wx/treectrl.h>
#include <wx/config.h>
#include <wx/utils.h>

#include <AtlasBase.h>

#include "StructEditView.h"
#include "ContentDataView.h"

enum
{
	ID_OBJECT_NAME = wxID_HIGHEST + 1,
	ID_OBJECT_TEXT,
	ID_OBJECT_GRID,
};

BEGIN_EVENT_TABLE(CStructEditView, wxPanel)
	EVT_PG_CHANGED(ID_OBJECT_GRID, CStructEditView::OnPropertyGridChange)
    EVT_PG_CHANGING(ID_OBJECT_GRID, CStructEditView::OnPropertyGridChanging)
END_EVENT_TABLE()

CStructEditView::CStructEditView(wxWindow* pParent, wxWindow* pDataViewWindow) : wxPanel(pParent)
{
	m_pSizer = ATLAS_NEW wxBoxSizer(wxVERTICAL);
	m_pText = ATLAS_NEW wxTextCtrl(this, ID_OBJECT_TEXT, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
	m_pText->Show(false);

	m_pPropGrid = ATLAS_NEW wxPropertyGrid(this, ID_OBJECT_GRID, wxDefaultPosition, wxDefaultSize, wxPG_SPLITTER_AUTO_CENTER | wxPG_DEFAULT_STYLE );
	m_pPropGrid->SetExtraStyle( wxPG_EX_HELP_AS_TOOLTIPS );
	m_pPropGrid->SetColumnProportion(0, 30);
	m_pPropGrid->SetColumnProportion(1, 70);

	m_pSizer->Add(m_pPropGrid, 1, wxGROW|wxALIGN_CENTER_VERTICAL);
	SetSizer(m_pSizer);
	m_data = NULL;
	Clear();

	m_pDataViewWindow = (CContentDataView*)pDataViewWindow;
}

CStructEditView::~CStructEditView()
{
	if(m_data)
	{
		ATLAS_FREE(m_data);
		m_data = NULL;
	}
}

void CStructEditView::SetText(const DDLReflect::STRUCT_INFO* info, const void* data)
{
	std::string json;
	DDLReflect::Struct2Json(info, (const _U8*)data, json);
	m_pText->SetValue(json.c_str());
}

bool CStructEditView::ValidataPropIsNull(wxPGProperty* prop, wxVariant& pendingValue)
{
	wxVariant value = prop->GetValue();
	if(!pendingValue.IsNull())
	{
		value = pendingValue;
	}
	
	PropClientData* pData = (PropClientData*)prop->GetClientData();
	_U8 type = pData->type;
	_U32 flags = pData->flags;

	bool bIsNull = false;
	if(flags & DDLReflect::FLAG_NOTNULL)
	{
		switch(type)
		{
		case DDLReflect::TYPE_UUID:
			{
				wxString strValue = value.GetString();
				A_UUID uuid;
				char szUUID[256];
				AUuidToString(uuid, szUUID);

				if(strValue == szUUID)
				{
					bIsNull = true;
				}
			}
			break;
		case DDLReflect::TYPE_STRING:
			{
				wxString strValue = value.GetString();
				if(strValue.empty())
				{
					bIsNull = true;
				}
			}
			break;
		case DDLReflect::TYPE_ARRAY:
			{
				wxString strValue = value.GetString();
				if(strValue == _("0"))
				{
					bIsNull = true;
				}
			}
			break;
		default:
			{

			}
			break;
		}
	}

	return bIsNull;
}

bool CStructEditView::Set(const DDLReflect::STRUCT_INFO* info, const void* data)
{
	m_bModify = false;
	if(m_data)
	{
		ATLAS_FREE(m_data);
		m_data = NULL;
	}

	m_pInfo = info;
	SetText(info, data);
	m_pPropGrid->Clear();

	m_data = ATLAS_ALLOC(info->size);
	memcpy(m_data, data, info->size);

	m_pDataAddr = (void*)data;

	return true;
}

bool CStructEditView::Get(const DDLReflect::STRUCT_INFO* info, void* data)
{
	return DDLReflect::Json2Struct(info, (std::string)m_pText->GetValue().c_str(), (_U8*)data);
}

void* CStructEditView::Undo()
{
	m_bModify = false;
	return m_data;
}

void CStructEditView::FlashView()
{
	SetText(m_pInfo, m_pDataAddr);
}

bool CStructEditView::IsDiffItem()
{
	A_CONTENT_OBJECT* object = (A_CONTENT_OBJECT*)m_pDataAddr;
	return (m_uuidLastModify == object->uuid);
}

void CStructEditView::ValidateNullValue(wxArrayString& strArrayString)
{
	wxPropertyGridIterator it = m_pPropGrid->GetIterator();

	while(!it.AtEnd())
	{
		wxPGProperty* property = *it;
		bool bValidate = ValidataPropIsNull(property);
		if(bValidate)
		{
			strArrayString.Add(property->GetName());
		}

		++it;
	}
}

void CStructEditView::Clear()
{
	m_pText->SetValue("");
	m_pPropGrid->Clear();
}

bool CStructEditView::IsModify()
{
	return m_bModify;
}

wxPGProperty* CStructEditView::CreatePropertyByType(wxString strValueName, _U8 type, const void* data, const _U32 flags, const char* ref_type, const char* struct_type)
{
	wxPGProperty* prop = NULL;
	switch(type)
	{
	case DDLReflect::TYPE_U8:		
		{
			_U8 value = 0; 
			memcpy(&value, data, sizeof(_U8));
			prop = ATLAS_NEW CPropertyEx<_U8, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_U16:	
		{
			_U16 value = 0; 
			memcpy(&value, data, sizeof(_U16));
			prop = ATLAS_NEW CPropertyEx<_U16, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_U32:	
		{
			_U32 value = 0; 
			memcpy(&value, data, sizeof(_U32));
			prop = ATLAS_NEW CPropertyEx<_U32, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_U64:	
		{
			_U64 value = 0; 
			memcpy(&value, data, sizeof(_U64));
			prop = ATLAS_NEW CPropertyEx<_U64, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_S8:
		{
			_S8 value = 0; 
			memcpy(&value, data, sizeof(_S8));
			prop = ATLAS_NEW CPropertyEx<_S8, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_S16:
		{
			_S16 value = 0; 
			memcpy(&value, data, sizeof(_S16));
			prop = ATLAS_NEW CPropertyEx<_S16, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_S32:	
		{
			_S32 value = 0; 
			memcpy(&value, data, sizeof(_S32));
			prop = ATLAS_NEW CPropertyEx<_S32, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_S64:
		{
			_S64 value = 0; 
			memcpy(&value, data, sizeof(_S64));
			prop = ATLAS_NEW CPropertyEx<_S64, wxIntProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_F32:	
		{
			_F32 value = 0; 
			memcpy(&value, data, sizeof(_F32));
			prop = ATLAS_NEW CPropertyEx<_F32, wxFloatProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_F64:
		{
			_F64 value = 0; 
			memcpy(&value, data, sizeof(_F64));
			prop = ATLAS_NEW CPropertyEx<_F64, wxFloatProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_STRING:
		{
			wxString value = (_STR)data;
			prop = ATLAS_NEW CPropertyEx<wxString, wxStringProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_UUID:
		{
			char name[1000];
			A_UUID uuid;
			memcpy(&uuid, data, sizeof(A_UUID));
			AUuidToString(uuid, name);
			wxString value = name;
			prop = ATLAS_NEW CPropertyEx<wxString, wxStringProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_STRUCT:
		{
			wxString value = struct_type;
			prop = ATLAS_NEW CPropertyEx<wxString, wxStringProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
			prop->ChangeFlag(wxPG_PROP_DISABLED, true);
		}
		break;
	case DDLReflect::TYPE_MASK:
		{
			assert(0);
		}
		break;
	case DDLReflect::TYPE_ARRAY:
		{
			wxString value = _("0");
			prop = ATLAS_NEW CPropertyEx<wxString, wxStringProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
		}
		break;
	case DDLReflect::TYPE_UUID_REF:
		{
			char name[1000];
			A_UUID uuid;
			memcpy(&uuid, data, sizeof(A_UUID));
			AUuidToString(uuid, name);
			wxString value = name;
			prop = ATLAS_NEW CPropertyEx<wxString, wxLongStringProperty>((void*)data, type, strValueName, wxPG_LABEL, value);
			((CPropertyEx<wxString, wxLongStringProperty>*)prop)->SetRefType(ref_type);
		}
		break;
	default:
		{
			assert(0);
			//prop = ATLAS_NEW wxStringProperty(strValueName, wxPG_LABEL, wxT("<composed>"));
		}
		break;
	}

	if(flags & DDLReflect::FLAG_READONLY)
	{
		prop->ChangeFlag(wxPG_PROP_DISABLED, true);
	}

	PropClientData* pCliData = ATLAS_NEW PropClientData;
	pCliData->type = type;
	pCliData->flags = flags;
	prop->SetClientData((wxPGProperty::ClientDataType)pCliData);

	return prop;
}

void CStructEditView::InitPropGrid(const DDLReflect::STRUCT_INFO* info, const void* data, bool bTopLevel, wxPGId parent_pgid)
{
	assert(info);
	if(info->parent)
	{
		InitPropGrid(info->parent, data, bTopLevel, parent_pgid);
	}
	
	for(_U32 i = 0; i < info->fcount; ++i)
	{
		DDLReflect::FIELD_INFO& finfos = info->finfos[i];
		
		_U32 count = 1;
		_U32 array_prefix = 0;
		wxPGId array_pid = 0;
		wxPGProperty* array_propGrid = NULL;
		wxString strStructType = _("");
		if(finfos.type & DDLReflect::TYPE_ARRAY)
		{
			assert(count < finfos.alen);
			count = finfos.alen;

			if(finfos.type & DDLReflect::TYPE_STRUCT)
			{
				count = finfos.alen;
				if(finfos.sinfo)
				{
					strStructType = finfos.sinfo->name;
				}
			}

			array_prefix = finfos.prefix;
			if(strStructType.empty())
			{
				array_propGrid = CreatePropertyByType(finfos.name, DDLReflect::TYPE_ARRAY, (_U8*)data + finfos.offset, finfos.flags);
			}
			else
			{
				array_propGrid = CreatePropertyByType(finfos.name, DDLReflect::TYPE_ARRAY, (_U8*)data + finfos.offset, finfos.flags, _(""), strStructType.c_str());
			}
			
			((CPropertyEx<wxString, wxStringProperty>*)(array_propGrid))->RereshSizeInfo(*(_U32*)((_U8*)data + finfos.offset));

			if(parent_pgid)
			{
				array_pid = m_pPropGrid->AppendIn(parent_pgid, array_propGrid);
			}
			else
			{
				array_pid = m_pPropGrid->Append(array_propGrid);
			}
		}
		else
		{
			if(finfos.type & DDLReflect::TYPE_STRUCT)
			{
				if(finfos.sinfo)
				{
					strStructType = finfos.sinfo->name;
				}
			}
		}

		if(finfos.type == DDLReflect::TYPE_STRUCT)
		{
			count = 1;
		}

		_U32 data_len = *(_U32*)((_U8*)data + finfos.offset);

		for(_U32 j = 0; j < count; ++j)
		{
			wxString strName;
			_U8 type = finfos.type;
			_U64 elem_offest = 0;
			if(count > 1)
			{
				char szName[100];
				sprintf(szName, "%s[%u]", finfos.name, j);
				strName = szName;
				type &= (~DDLReflect::TYPE_ARRAY);
				
				if(type == DDLReflect::TYPE_STRUCT)
				{
					elem_offest =  finfos.sinfo->size * j + array_prefix;
					
				}
				else
				{
					elem_offest =  finfos.elen * j + array_prefix;
				}

				//array_prefix = 0;
			}
			else
			{
				strName = finfos.name;
			}

			wxPGProperty* propGrid;
			if(strStructType.empty())
			{
				propGrid = CreatePropertyByType(strName, type, (_U8*)data + finfos.offset + elem_offest, finfos.flags, finfos.ref_type);
			}
			else
			{
				propGrid = CreatePropertyByType(strName, type, (_U8*)data + finfos.offset + elem_offest, finfos.flags, finfos.ref_type, strStructType.c_str());
			}

			assert(propGrid);	
			if(finfos.type & DDLReflect::TYPE_ARRAY)
			{
				if(data_len == 0)
				{
					propGrid->Hide(true);
				}
				else
				{
					data_len--;
				}
			}
			
			if(bTopLevel)
			{
				wxPGId pid = 0;
				if(array_pid)
				{
					//pid = m_pPropGrid->AppendIn(array_pid, propGrid);
					wxPGProperty* prop = array_propGrid->AppendChild(propGrid);
					pid = prop->GetId();
					if(finfos.sinfo)
					{
						InitPropGrid(finfos.sinfo, (_U8*)data + finfos.offset + elem_offest, false, pid); //parse struct will enter
					}
				}
				else
				{
					m_pPropGrid->Append( ATLAS_NEW wxPropertyCategory(info->name));
					m_parentID = m_pPropGrid->Append(propGrid);
					if(finfos.sinfo)
					{
						InitPropGrid(finfos.sinfo, (_U8*)data + finfos.offset + elem_offest, false, m_parentID); //parse struct will enter
					}
				}
			}
			else
			{
				wxPGId pid = 0;
				if(finfos.type & DDLReflect::TYPE_ARRAY)
				{
					//pid = m_pPropGrid->AppendIn(array_pid, propGrid);
					wxPGProperty* prop = array_propGrid->AppendChild(propGrid);
					pid = prop->GetId();
				}
				else
				{
					pid = m_pPropGrid->AppendIn(parent_pgid, propGrid);
				}
				
				if(finfos.sinfo)
				{
					InitPropGrid(finfos.sinfo, (_U8*)data + finfos.offset + elem_offest, false, pid);
				}
			}
		}

	}

}

void CStructEditView::OnPropertyGridChange(wxPropertyGridEvent &event)
{
    wxPGProperty* p = event.GetProperty();
	wxString strName = p->GetName().c_str();
	wxString strValue = p->GetValueAsString();
    if(p)
	{
		wxLogDebug(wxT("OnPropertyGridChange(%s, value=%s)"), strName.c_str(), strValue.c_str());
	}
    else
	{
		wxLogDebug(wxT("OnPropertyGridChange(NULL)"));
	}

	wxVariant value = p->GetValue();
	ModifyValue(p, value);
	FlashView();

	A_CONTENT_OBJECT* object = (A_CONTENT_OBJECT*)m_pDataAddr;
	if(m_uuidLastModify.isEmpty())
	{
		m_uuidLastModify = object->uuid;
	}
		
	if(m_uuidLastModify != object->uuid)
	{
		m_uuidLastModify = object->uuid;
	}
		
	if(p->GetName() == _("name"))
	{
		m_pDataViewWindow->UpdateItemName(strValue, m_uuidLastModify);
	}
}

void CStructEditView::OnPropertyGridChanging(wxPropertyGridEvent &event)
{
	m_bModify = true;
    wxPGProperty* property = event.GetProperty();
	
	wxVariant pendingValue = event.GetValue();
	bool bValidate = ValidataPropIsNull(property, pendingValue);

	if (pendingValue.IsNull() || bValidate)
	{
		wxLogDebug(wxT("OnPropertyGridChanging()"));
		event.Veto();
		event.SetValidationFailureBehavior(wxPG_VFB_STAY_IN_PROPERTY | wxPG_VFB_BEEP | wxPG_VFB_SHOW_MESSAGE);
		wxMessageBox(_("This feild must have not null value!!!"));

		m_pPropGrid->SetFocus();
		m_pPropGrid->UpdateWindowUI();
		m_pPropGrid->Update();
	}
}

void CStructEditView::SetDebugModel()
{
	static bool bFirst = true;
	if(bFirst)
	{
		m_pSizer->Add(m_pText, 1, wxGROW|wxALIGN_CENTER_VERTICAL);
		bFirst = !bFirst;
	}

	static bool bShow = false;
	if(bShow)
	{
		m_pText->Show(false);
		m_pSizer->Show((size_t)1, false);
	}
	else
	{
		m_pText->Show(true);
		m_pSizer->Show((size_t)1, true);
	}
	bShow = !bShow;

	m_pSizer->Layout();
}

bool CStructEditView::UpdateGrid()
{
	m_pPropGrid->UnfocusEditor();
	m_pPropGrid->UpdateWindowUI();
	m_pPropGrid->Update();

	return true;
}